#include <fcntl.h>
#include <errno.h>
#include "InSdkCheckNetStatus.h"

#define DEF_TAG "CheckNet"

Com_u8 g_InSdkNetStatus_ConnectFlag = DEF_COM_FALSE;
StSdkNetStatusHostInfo g_InSdkNetStatus_BaiDuHostInfo = {0};

/**
 * 带超时的connect函数
 * @param sockfd: 套接字描述符
 * @param addr: 目标地址结构体
 * @param addrlen: 地址结构体长度
 * @param timeout_sec: 超时时间(秒)
 * @return: 0成功, -1失败
 */
int Ext_InSdkNetStatus_ConnectWithTimeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_sec)
{
    int ret;
    fd_set writefds;
    struct timeval timeout;
    int flags;

    // 保存原始套接字标志
    if ((flags = fcntl(sockfd, F_GETFL, 0)) < 0)
    {
        perror("fcntl(F_GETFL) failed");
        return -1;
    }

    // 设置套接字为非阻塞模式
    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0)
    {
        perror("fcntl(F_SETFL) failed");
        return -1;
    }

    // 发起非阻塞连接
    ret = connect(sockfd, addr, addrlen);
    // 连接立即成功(极少发生，通常在本地连接时)
    if (ret == 0)
    {
        // 恢复原始模式
        fcntl(sockfd, F_SETFL, flags);
        return 0;
    }

    // 检查错误是否为EINPROGRESS或EWOULDBLOCK(表示连接正在进行)
    if (errno != EINPROGRESS && errno != EWOULDBLOCK)
    {
        perror("connect failed immediately");
        fcntl(sockfd, F_SETFL, flags);
        return -1;
    }

    // 使用select等待连接完成
    FD_ZERO(&writefds);
    FD_SET(sockfd, &writefds);
    timeout.tv_sec = timeout_sec;
    timeout.tv_usec = 0;

    // 等待套接字可写(连接完成的标志)
    ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
    if (ret < 0)
    {
        perror("select failed");
        fcntl(sockfd, F_SETFL, flags);
        return -1;
    }
    // select超时
    else if (ret == 0)
    {
        fprintf(stderr, "connect timeout after %d seconds\n", timeout_sec);
        // 必须关闭，避免资源泄漏
        close(sockfd);
        return -1;
    }

    // 检查连接是否真的成功(select返回可写可能是错误)
    int error = 0;
    socklen_t len = sizeof(error);
    if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
    {
        perror("getsockopt failed");
        fcntl(sockfd, F_SETFL, flags);
        return -1;
    }

    // 连接失败
    if (error != 0)
    {
        errno = error;
        perror("connect failed");
        close(sockfd);
        return -1;
    }

    // 恢复套接字原始模式
    fcntl(sockfd, F_SETFL, flags);
    // 连接成功
    return 0;
}

/**
* StSdkNetStatusHostInfo host_info 不能重复定义，所以要放在循环之外
**/
Com_s32 Ext_InSdkNetStatus_ConnectServerResult(pStSdkNetStatusHostInfo host_info)
{
    int ret = 0;
    if (g_InSdkNetStatus_ConnectFlag == DEF_COM_TRUE)
    {
        ret = -1;
        goto connect_exit;
    }
    g_InSdkNetStatus_ConnectFlag = DEF_COM_TRUE;

    if (host_info->flag == 0)
    {
        host_info->flag = 1;
        struct hostent *host;
        host = gethostbyname(host_info->host);
        if ((host == NULL) || (host->h_addr_list[0] == NULL))
        {
            res_init();
            ret = -1;
            goto connect_exit;
        }

        host_info->addr = *(struct in_addr *)(host->h_addr_list[0]);
        res_init();
    }

    int i;
    int skt;
    struct sockaddr_in sa;

    memset(&sa, 0, sizeof(sa));

    sa.sin_addr = host_info->addr;
    sa.sin_port = htons(host_info->port);
    sa.sin_family = AF_INET;

    if ((skt = (int)socket(AF_INET, SOCK_STREAM, 0)) >= 0)
    {
        for (i = 0; i < 2; i++)
        {
            if (Ext_InSdkNetStatus_ConnectWithTimeout(skt, (struct sockaddr *)&sa, sizeof(struct sockaddr_in),3) == 0)
            {
                break;
            }
        }
        if (i == 2)
        {
            ret = -1;
        }

        close(skt);
    }
    else
    {
        ret = -1;
    }

connect_exit:
    g_InSdkNetStatus_ConnectFlag = DEF_COM_FALSE;
    return ret;
}

Com_s32 Ext_InSdkNetStatus_Check(Com_void)
{
    Com_s32 ret = -1;

    g_InSdkNetStatus_BaiDuHostInfo.port = 80;
    strncpy(g_InSdkNetStatus_BaiDuHostInfo.host, "www.baidu.com", sizeof(g_InSdkNetStatus_BaiDuHostInfo.host));
    ret = Ext_InSdkNetStatus_ConnectServerResult(&g_InSdkNetStatus_BaiDuHostInfo);
    return ret;
}
